//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Text.RegularExpressions;
using System.Web.Caching;
using CommunityServer.Blogs.Components;
using CommunityServer.Components;

namespace CommunityServer.Blogs.Components
{
    /// <summary>
    /// Provides read and write access to specific blog posts
    /// </summary>
	public class WeblogPosts
    {
        /// <summary>
        /// Can not be instantiated
        /// </summary>
        private WeblogPosts(){}

        #region CreateUpdateDelete

		#region Preview

		public static void PreviewFormat(WeblogPost post)
		{
			PostFormatting(post);
            CSEvents.PrePost(post,post.PostID <= 0 ? ObjectState.Create : ObjectState.Update, ApplicationType.Weblog);
			CSEvents.BeforePost(post,post.PostID <= 0 ? ObjectState.Create : ObjectState.Update,ApplicationType.Weblog);
		}

		#endregion

        #region Add

        /// <summary>
        /// Adds a new weblog post
        /// </summary>
        public static BlogPostResults Add(WeblogPost post, User user, out int postID)
        {
			post.User = user;

			PostFormatting(post);

			CSEvents.PrePost(post,ObjectState.Create,ApplicationType.Weblog);
			CSEvents.BeforePost(post,ObjectState.Create,ApplicationType.Weblog);

			if(post.IsPostEnabled && post.EverTracked == false)
			{
				post.EverTracked = true;
				post.EnableExternalNotificatons = true;
			}

            WeblogDataProvider wdp = WeblogDataProvider.Instance();

            BlogPostResults result = wdp.AddPost(post,user, out postID);
            post.PostID = postID;

			
			if (result == BlogPostResults.Success && postID > 0)
            {
				CSEvents.AfterPost(post, ObjectState.Create, ApplicationType.Weblog);
            }

            if(post.IsApproved)
                ExpireCategoryTags(post.Weblog);

			return result;
        }

        /// <summary>
        /// Adds a new weblog post. The user is set to the current user
        /// </summary>
        public static void Add(WeblogPost entry, User user)
        {
            int pid = -1;
            Add(entry,user, out pid);
        }

        public static void Add(WeblogPost entry)
        {
            Add(entry,CSContext.Current.User);
        }

        #endregion

        #region Update

        public static BlogPostResults Update(WeblogPost post)
        {
           return Update(post,CSContext.Current.User.UserID);
        }

        public static BlogPostResults Update(WeblogPost post, int userID)
        {
            return Update(post, userID, true);
        }

        public static BlogPostResults Update(WeblogPost post, int userID, bool formatPost)
        {
            if(post.User == null)
                post.User = CSContext.Current.User;

            // Allow internal components to skip formatting for "quick" updates such as approving a post
            if (formatPost)
                PostFormatting(post);

			CSEvents.PrePost(post,ObjectState.Update,ApplicationType.Weblog);
            CSEvents.BeforePost(post,ObjectState.Update,ApplicationType.Weblog);

			// This needs to be outside PostFormatting, because "quick" updates will skip that (i.e. toggling approved)
			if(post.IsPostEnabled && post.EverTracked == false)
			{
				post.EverTracked = true;
				post.EnableExternalNotificatons = true;
			}

            WeblogDataProvider wdp = WeblogDataProvider.Instance();
            BlogPostResults results = wdp.UpdatePost(post,userID);

            if(results == BlogPostResults.Success)
            {
                CSEvents.AfterPost(post,ObjectState.Update,ApplicationType.Weblog);

                if (post.BlogPostType == BlogPostType.Post || post.BlogPostType == BlogPostType.Article)
                {
                    CSCache.RemoveByPattern(string.Format("B:{0}-P:{1}-PN:{2}", post.SectionID, post.PostID, post.HasName ? Regex.Escape(post.Name) : null));
                ExpireCategoryTags(post.Weblog);
            }
            }

            return results;
        }

       #endregion

        #region Delete

        /// <summary>
        /// Deletes the specified post.
        /// </summary>
        public static void Delete(WeblogPost entry, int UserID)
        {
            CSEvents.PrePost(entry,ObjectState.Delete,ApplicationType.Weblog);
            CSEvents.BeforePost(entry,ObjectState.Delete,ApplicationType.Weblog);

            EventLogs.Warn(string.Format("{3} ({0}): {1} was deleted by {2}", entry.PostID,entry.Subject, UserID,entry.BlogPostTypeName), "Weblogs", 444,CSContext.Current.SettingsID);
            
            WeblogDataProvider wdp = WeblogDataProvider.Instance();
            wdp.DeletePost(entry.SectionID, entry.PostID, UserID, entry.BlogPostType);
            
			ClearPostsCache(entry.SectionID, entry.PostID);
			
			CSEvents.AfterPost(entry,ObjectState.Delete,ApplicationType.Weblog);
		}

        /// <summary>
        /// Deletes the specified post.
        /// </summary>
		public static void Delete(int sectionID, int postID, BlogPostType blogPostType, int userID)
		{
			WeblogDataProvider wdp = WeblogDataProvider.Instance();
			WeblogPost post = wdp.GetWeblogPost(postID,false,false);
            Delete(post,userID);
		}

		public static void ClearPostsCache(int sectionID, int postID)
		{
			CSContext cSContext = CSContext.Current;

			// Indiv Post
			CSCache.RemoveByPattern(string.Format("P{0}", postID));

			// BlogThreadQuery
			CSCache.RemoveByPattern(string.Format("B:{0}-P:{1}", sectionID, postID));
			CSCache.RemoveByPattern(string.Format("B:-1-P:{1}", sectionID, postID));
			CSCache.RemoveByPattern("B:-1-P:-1");
		}

        #endregion

        #region Post Helpers

		public static readonly string PostNamePattern = "^[0-9]*[^0-9].{0,224}$";
		protected static Regex spacer = new Regex(@"\s{2,}", RegexOptions.Compiled | RegexOptions.Singleline);
		protected static Regex postNameInvalidChars = new Regex(@"[^0-9a-zA-Z ]+", RegexOptions.Compiled | RegexOptions.Singleline);

        /// <summary>
        /// Provides formatting/security support on weblog posts
        /// </summary>
        internal static void PostFormatting(WeblogPost post)
        {
			CSContext csContext = CSContext.Current;

			if(post.HasName)
			{
				if(!Regex.IsMatch(post.Name, PostNamePattern, RegexOptions.IgnoreCase))
					throw new ArgumentException(string.Format("Invalid Post Name format. Must match pattern {0}", PostNamePattern));

			}
            else if((post.BlogPostType == BlogPostType.Post || post.BlogPostType == BlogPostType.Article) && post.Weblog.AutoNamePosts)
			{
				string name = spacer.Replace(postNameInvalidChars.Replace(post.Subject, " "), " ").Trim().ToLower();
				if(name.Length > 225)
					name = name.Substring(0,225).Trim();
                
				if(Regex.IsMatch(name,PostNamePattern,RegexOptions.IgnoreCase))
					post.Name = name;
			}

			post.SubmittedUserName = Globals.HtmlEncode((post.SubmittedUserName));
			post.Subject = Globals.HtmlEncode(post.Subject);

            if(post.PostID <= 0)
            {
                // Are we tracking the post IP address?
                if (csContext.IsWebRequest && csContext.SiteSettings.EnableTrackPostsByIP)
                {
                	string ip = csContext.Context.Request.UserHostAddress;
					if(ip != null)
						post.UserHostAddress = ip;
                }
            }

			if(post.BlogPostType == BlogPostType.Post || post.BlogPostType == BlogPostType.Article)
			{
				//We are enabling other parts of the application to pre-process the post data and store the original in the body
				//property. If we find a value in the .FormattedBody property we will use it as our base
				if(Globals.IsNullorEmpty(post.FormattedBody))
					post.FormattedBody = post.Body;
			}
			else
			{
				post.FormattedBody = post.Body;
			}

        }
        #endregion


        #endregion

        #region GetPost

        /// <summary>
        /// Returns the specificed WeblogPost
        /// </summary>
        public static WeblogPost GetPost(int PostID, bool includeCategories, bool isApproved, bool useCache)
        {
			CSContext cntx = CSContext.Current;
            string key = string.Format("P{0}Roles{1}IC{2}IA{3}C:{4}",PostID,cntx.RolesCacheKey,includeCategories,isApproved,useCache);
            WeblogPost we = cntx.Items[key] as WeblogPost;
            if(we == null)
            {
                if(useCache)
                    we = CSCache.Get(key) as WeblogPost;

                if(we == null)
                {
                    WeblogDataProvider wdp = WeblogDataProvider.Instance();
                    we = wdp.GetWeblogPost(PostID,includeCategories,isApproved);
                    if(useCache && we != null)
                        CSCache.Insert(key,we,1 * CSCache.MinuteFactor,CacheItemPriority.BelowNormal);
                 
                }

               cntx.Items.Add(key,we);
            }
            return we;
        }

        /// <summary>
        /// Returns the specified Weblog posts. Caching and filtering on are by default
        /// </summary>
        public static WeblogPost GetWeblogEntry(int WeblogID, int PostID)
        {
            return GetPost(PostID,false,true,true);
        }

        #endregion

        #region GetThreadSet

        public static ScoredPostList GetScoredBlogThreads(BlogThreadQuery query, bool cachable)
        {
            //copy settings since we will override them
            int pageSize = query.PageSize;
            int pageIndex = query.PageIndex;

            //override query settings
            query.PageSize = WeblogConfiguration.Instance(true).PostsToScore;
            query.PageIndex = 0;
            query.FirstPageOnly = true;

            ThreadSet _threads = WeblogPosts.GetBlogThreads(query, cachable, true);
            IList posts = BlogRelevantPostScoringProvider.Instance().Score(_threads.Threads, pageIndex, pageSize);

            return new ScoredPostList(posts, pageIndex, pageSize, _threads.TotalRecords);
        }

        public static ThreadSet GetBlogThreads(BlogThreadQuery query)
        {
            return GetBlogThreads(query,true);
        }

        public static ThreadSet GetBlogThreads(BlogThreadQuery query, bool cacheable)
        {
            return GetBlogThreads(query,cacheable,false);
        }

        public static ThreadSet GetBlogThreads(BlogThreadQuery query, bool cacheable, bool isAggregate)
        {
            CSContext context = CSContext.Current;
            string key = query.Key;
            ThreadSet ts = null;
            
            if(cacheable)
                ts = CSCache.Get(key) as ThreadSet;

            if(cacheable && ts == null)
                ts = context.Items[key] as ThreadSet;

            if(ts == null)
            {
                query.IsCacheable = cacheable;
                query.IsAggregate = isAggregate;

                WeblogDataProvider wdp = WeblogDataProvider.Instance();
                ts = wdp.GetBlogThreads(query);

                //NOTE: This is too late to apply this filter. We could end up in bad paging/etc
//                if (isAggregate)
//                {
//                    // Filter out any posts that belong to a blog that is not community aggregated
//                    ts.Threads = WeblogPosts.FilterByWeblogAggregated(ts.Threads, true);
//                }

                if(cacheable)
                {
                    if(query.BlogThreadType == BlogThreadType.Recent && (query.Tags == null || query.Tags.Length == 0))
                    {
                        if(!isAggregate)
                        {
                            if(query.PageIndex == 0)
                            {
                                CSCache.Insert(key,ts,CSCache.MinuteFactor * 3, CacheItemPriority.Normal);
                            }
                        }
                        else
                        {
                            if(query.PageIndex == 0)
                            {
                                CSCache.Insert(key,ts,CSCache.MinuteFactor * 10, CacheItemPriority.High);
                            }
                            else
                            {
                                CSCache.Insert(key,ts,CSCache.SecondFactorCalculate(30), CacheItemPriority.Low);
                            }
                        }
                    }
                    //make sure we only do this once per request
                    context.Items[key] = ts;

                }
            }

            return ts;
        }

        #endregion

        #region PostSet

		public static PostSet GetTrackedThreadsByUser(int BlogID, int UserID)
		{
			WeblogDataProvider wdp = WeblogDataProvider.Instance();
			return wdp.GetThreadsTrackedByUser(BlogID,UserID);
		}

        public static PostSet GetPosts(BlogThreadQuery query, bool cacheable)
        {
            CSContext cntx = CSContext.Current;
            string key = cacheable ? query.Key : null;
            PostSet ps = null;
            
            if(cacheable)
                ps = CSCache.Get(key) as PostSet;

            if(cacheable && ps == null)
                ps = cntx[key] as PostSet;

            if(ps == null)
            {
                WeblogDataProvider wdp = WeblogDataProvider.Instance();
                ps = wdp.GetPosts(query);

                if(cacheable && ps != null)
                {
                    if(ps.Organize() && ps.ThreadStarter.PostDate >= DateTime.Now.AddDays(-7))
                        CSCache.Insert(key,ps,CSCache.MinuteFactor * 2,System.Web.Caching.CacheItemPriority.Low);

                    //make sure we only do this once per request
                    cntx[key] = ps;
                }

                
            }

            return ps;

        }
        #endregion

		/// <summary>
		/// Returns a list of ArchiveDataItems specifying the number of posts made each month
		/// </summary>
		/// <param name="BlogID">ID of blog to look up</param>
        public static ArrayList GetPostsByMonths(int BlogID)
        {
			string cacheKey = "BlogMonths" + BlogID.ToString();
			ArrayList al = CSCache.Get(cacheKey) as ArrayList;
			if(al == null)
			{
				WeblogDataProvider wdp = WeblogDataProvider.Instance();
				al= wdp.GetPostsByMonths(BlogID);
				CSCache.Insert(cacheKey,al,CSCache.MinuteFactor * 2, CacheItemPriority.Low);
                
			}
			return al;

        }

		/// <summary>
		/// Returns a list of days in the specified month containing posts. The type must be BlogPost and the 
		/// BloggerTime must be greater than DateTime.Now
		/// </summary>
		public static Hashtable GetPostsByMonth(int BlogID, DateTime Month)
		{
			string cacheKey = string.Format("B:{0}-M:{1}-Y:{2}",BlogID,Month.Month,Month.Year);
			Hashtable ht = CSCache.Get(cacheKey) as Hashtable;
			if(ht == null)
			{
				WeblogDataProvider wdp = WeblogDataProvider.Instance();
				ht = wdp.GetPostsByMonth(BlogID,Month);
				CSCache.Insert(cacheKey,ht,CSCache.MinuteFactor * 5, CacheItemPriority.High);				 
			}
			return ht;
		}


        public static ArrayList GetCategoryTags(WeblogPost post)
        {
            ArrayList list = new ArrayList();
            if(post.HasCategories)
            {
                Hashtable ht = GetCategoryTags(post.Weblog);
                foreach(string c in post.Categories)
                {
                    CategoryTag tag = ht[c] as CategoryTag;
                    if(tag != null)
                        list.Add(tag);
                }
            }
            return list;
        }

		public static Hashtable GetCategoryTags(Weblog weblog)
		{
			string key = string.Format("CategoryTags:{0}:{1}", weblog.SectionID.ToString(), weblog.SettingsID);
			Hashtable ht = CSCache.Get(key) as Hashtable;
			if(ht == null)
			{
				ArrayList categories = PostCategories.GetCategories(weblog.SectionID,false, false);
				CSContext context = CSContext.Current;
				BlogUrls urls = BlogUrls.Instance();
				ht = new Hashtable(categories.Count);
				string hp = context.HostPath;
				foreach(PostCategory pc in categories)
				{
					CategoryTag tag = new CategoryTag(pc);
					tag.Link = hp + urls.IndividualCategoryPage(weblog.ApplicationKey,pc.CategoryID);
                        
					ht[pc.Name] = tag;
				}

				CSCache.Insert(key,ht,10);
			}

			return ht;
		}

        public static void ExpireCategoryTags(Weblog weblog)
        {
            string key = string.Format("CategoryTags:{0}:{1}", weblog.SectionID.ToString(), weblog.SettingsID);
            CSCache.Remove(key);
        }

		/// <summary>
		/// Filters posts by the parent weblog IsCommunityAggregated value
		/// </summary>
		public static ArrayList FilterByWeblogAggregated(ArrayList posts, bool isSectionCommunityAggregated)
		{
			ArrayList filteredList = new ArrayList();
			foreach(WeblogPost wp in posts)
			{
				if(wp.Weblog.IsCommunityAggregated == isSectionCommunityAggregated) 
					filteredList.Add(wp);
			}
			return filteredList;
		}
    }

}
